package com.meshnetics.gb.stack.core.config;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.HashMap;

/**
 * Parser for InformationBase parameters.
 * Contains static methods for retreiving Class and value from config config file.
 * 
 * @author mmarkov
 */
public class IBParamParser {
    /** Simple types table with 'type name' <-> 'type class' mappings. */
    private static final Map TYPES = new HashMap();

    /** Array types table with 'array type name' <-> 'array type class' mappings. */
    private static final Map ARRAY_TYPES = new HashMap();

    static {
        TYPES.put("string", String.class);
        TYPES.put("integer", Integer.class);
        TYPES.put("int", Integer.class);
        TYPES.put("double", Double.class);
        TYPES.put("float", Float.class);
        TYPES.put("short", Short.class);
        TYPES.put("byte", Byte.class);
        TYPES.put("octet", Byte.class);
        TYPES.put("long", Long.class);
        TYPES.put("boolean", Boolean.class);

        ARRAY_TYPES.put("[integer", int[].class);
        ARRAY_TYPES.put("[int", int[].class);
        ARRAY_TYPES.put("[double", double[].class);
        ARRAY_TYPES.put("[float", float[].class);
        ARRAY_TYPES.put("[short", short[].class);
        ARRAY_TYPES.put("[byte", byte[].class);
        ARRAY_TYPES.put("[octet", byte[].class);
        ARRAY_TYPES.put("[long", long[].class);
        ARRAY_TYPES.put("[boolean", boolean[].class);
    }

    /**
     * Parses type parameter, creates and returns corresponding primitive Class
     * (or array of primitive classes).
     *
     * @param type String name of the Class to be decoded
     * @return decoded Class
     */
    public static Class getPrimitiveClass(String type) {
        Class clType;

        if (type.startsWith("[")) {
            clType = (Class) ARRAY_TYPES.get(type.toLowerCase());
        } else {
            clType = (Class) TYPES.get(type.toLowerCase());
        }
        return clType;
    }

    /**
     * Parses type parameter, creates and returns corresponding non-primitive Class
     * (or array of non-primitive classes).
     *
     * @param type String name of the Class to be decoded
     * @return decoded Class
     * @throws ClassNotFoundException if could not found class for the given type
     */
    public static Class getNonPrimitiveClass(String type) throws ClassNotFoundException {
        Class clType;

        if (type.startsWith("[")) {
            clType = Array.newInstance(Class.forName(type.substring(1)), 1).getClass();
        } else {
            clType = Class.forName(type);
        }
        return clType;
    }

    /**
     * Parses value parameter, creates and returns an Object of 'cl' type (possibly array)
     * containing the given value.
     *
     * @param cl Class of target Object
     * @param value String value to be decoded
     * @return created Object of 'cl' Class containing the given value
     * @throws NumberFormatException if could not parse the given value for primitive types
     * @throws RuntimeException if could not instantiate non-primitive class
     */
    public static Object getValue(Class cl, String value) {
        return cl.isArray() ? getArray(cl, value) : getObject(cl, value);
    }

    /**
     * Returns array with 1 element of given class-type.
     * This element contains an Object of the given type and contains the given value
     *
     * @param cl non-primitive type for array
     * @param value String value
     * @return created array
     */
    private static Object getArray(Class cl, String value) {
        Object res = Array.newInstance(cl.getComponentType(), 1);

        if (value != null && value.length() != 0) {
            ((Object []) res)[0] = getObject(cl.getComponentType(), value);
        }
        return res;
    }

    /**
     * Parses value parameter, creates and returns an Object of 'cl' non-array type
     * containing the given value.
     *
     * @param cl non-primitive Class of target Object
     * @param value String value to be decoded
     * @return created Object of 'cl' Class containing the given value
     * @throws NumberFormatException if could not parse the given value for primitive types
     * @throws RuntimeException if could not instantiate non-primitive class
     */
    private static Object getObject(Class cl, String value) {
        if(value == null) {
            return null;
        }

        Object res;

        if (cl == String.class) {
            res = value;
        } else if (cl == Byte.class) {
            res = new Byte(Long.decode(value).byteValue());
        } else if (cl == Short.class) {
            res = new Short(Long.decode(value).shortValue());
        } else if (cl == Integer.class) {
            res = new Integer(Long.decode(value).intValue());
        } else if (cl == Long.class) {
            res = Long.decode(value);
        } else if (cl == Float.class) {
            res = new Float(value);
        } else if (cl == Double.class) {
            res = new Double(value);
        } else if (cl == Boolean.class) {
            res = Boolean.valueOf(value);
        } else {
            res = getThroughRef(cl, value);
        }
        return res;
    }

    /**
     * Parses value parameter, creates and returns an Object of 'cl' non-array non-primitive type
     * containing the given value.
     *
     * @param cl Class of target Object
     * @param value String value to be decoded
     * @return created Object of 'cl' Class containing the given value
     * @throws RuntimeException if any error occured during result creation
     */
    private static Object getThroughRef(Class cl, String value) {
        Object res;

        if (value == null || value.length() == 0) {
            try {
                res = cl.newInstance();
            } catch (Exception ex) {
                throw new RuntimeException("Unable to instantiate " + cl.getName()
                        + " with no-arg constructor", ex);
            }
        } else {
            Constructor constr = null;
            Method m = null;

            try {
                constr = cl.getConstructor(new Class[]{ String.class });
            } catch (Exception ex) {
                try {
                    m = cl.getDeclaredMethod("valueOf", new Class[] { String.class });
                } catch (Exception ex1) {
                    throw new RuntimeException(
                            "Unable to get constructor with 1 String parameter or method "
                            + "'valueOf(String)' for class '" + cl.getName() + "'", ex1);
                }
            }

            if (constr != null) {
                try {
                    res = constr.newInstance(new Object[]{ value });
                } catch (Exception ex) {
                    throw new RuntimeException("Unable to instantiate " + cl.getName()
                            + " using constructor with 1 String parameter with value '"
                            + value + "'", ex);
                }
            } else {
                Object obj;

                try {
                    obj = cl.newInstance();
                } catch (Exception ex) {
                    throw new RuntimeException("Unable to instantiate " + cl.getName()
                            + " using no-arg constructor", ex);
                }

                try {
                    res = m.invoke(obj, new Object[] { value });
                } catch (Exception ex) {
                    throw new RuntimeException("Unable to call 'valueOf(String)' method for "
                            + cl.getName(), ex);
                }
            }
        }
        return res;
    }
}
